// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause

#include "vtkAxisExtended.h"

#include "vtkMath.h" // for VTK_DBL_EPSILON
#include "vtkObjectFactory.h"

#include <algorithm>
#include <cmath>
#include <sstream>
#include <vector>

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkAxisExtended);

vtkAxisExtended::vtkAxisExtended()
{
  this->FontSize = 0;
  this->DesiredFontSize = 10;
  this->Precision = 3;
  this->LabelFormat = 0;
  this->Orientation = 0;
  this->LabelLegibilityChanged = true;
  this->IsAxisVertical = false;
}

vtkAxisExtended::~vtkAxisExtended() = default;

// This method return a value to make step sizes corresponding to low q and j values more preferable
double vtkAxisExtended::Simplicity(
  int qIndex, int qLength, int j, double lmin, double lmax, double lstep)
{
  double eps = VTK_DBL_EPSILON * 100;
  int v = 1;
  ++qIndex;

  double rem = fmod(lmin, lstep);
  if ((rem < eps || (lstep - rem) < eps) && lmin <= 0 && lmax >= 0)
  {
    v = 0;
  }
  else
  {
    v = 1; // v is 1 is lebelling includes zero
  }

  return 1.0 - (qIndex - 1.0) / (qLength - 1.0) - j + v;
}

// This method returns the maximum possible value of simplicity value given q
// and j
double vtkAxisExtended::SimplicityMax(int qIndex, int qLength, int j)
{
  int v = 1;
  ++qIndex;
  return 1.0 - (qIndex - 1.0) / (qLength - 1.0) - j + v;
}

// This method makes the data range approximately same as the labeling range
// more preferable
double vtkAxisExtended::Coverage(double dmin, double dmax, double lmin, double lmax)
{
  double coverage =
    1.0 - 0.5 * (pow(dmax - lmax, 2) + pow(dmin - lmin, 2) / pow(0.1 * (dmax - dmin), 2));
  return coverage;
}

// This gives the maximum possible value of coverage given the step size
double vtkAxisExtended::CoverageMax(double dmin, double dmax, double span)
{
  double range = dmax - dmin;
  if (span > range)
  {
    double half = (span - range) / 2;
    return 1 - 0.5 * (pow(half, 2) + pow(half, 2) / pow(0.1 * (range), 2));
  }
  else
  {
    return 1.0;
  }
}

// This method return a value to make the density of the labels close to the
// user given value
double vtkAxisExtended::Density(int k, double m, double dmin, double dmax, double lmin, double lmax)
{
  double r = (k - 1) / (lmax - lmin);
  double rt = (m - 1) / (std::max(lmax, dmax) - std::min(dmin, lmin));

  return 2 - std::max(r / rt, rt / r);
}

// Derives the maximum values for density given k (number of ticks) and m
// (user given)
double vtkAxisExtended::DensityMax(int k, double m)
{
  if (k >= m)
  {
    return 2 - (k - 1) / (m - 1);
  }
  else
  {
    return 1;
  }
}

// This methods gives a weighing factor for each label depending on the range
// The coding for the different formats
//   1 - Scientific 5 * 10^6
//   2 - Decimal e.g. 5000
//   3 - K e.g. 5K
//   4 - Factored K e.g. 5(K)
//   5 - M e.g. 5M
//   6 - Factored M e.g. 5(M)
//   7 - Factored Decimals e.g. 5 (thousands)
//   8 - Factored Scientific 5 (10^6)
double vtkAxisExtended::FormatLegibilityScore(double n, int format)
{
  switch (format)
  {
    case 1:
      return 0.25;
    case 2:
      if (std::abs(n) > 0.0001 && std::abs(n) < 1000000)
      {
        return 1.0;
      }
      else
      {
        return 0.0;
      }
    case 3:
      if (std::abs(n) > 1000 && std::abs(n) < 1000000)
      {
        return 0.75;
      }
      else
      {
        return 0.0;
      }
    case 4:
      if (std::abs(n) > 1000 && std::abs(n) < 1000000)
      {
        return 0.4;
      }
      else
      {
        return 0.0;
      }
    case 5:
      if (std::abs(n) > 1000000 && std::abs(n) < 1000000000)
      {
        return 0.75;
      }
      else
      {
        return 0.0;
      }
    case 6:
      if (std::abs(n) > 1000000 && std::abs(n) < 1000000000)
      {
        return 0.4;
      }
      else
      {
        return 0.0;
      }
    case 7:
      return 0.5;
    case 8:
      return 0.3;
    default:
      return 0.0;
  }
}

// This method returns the length of the label given the format
int vtkAxisExtended::FormatStringLength(int format, double n, int precision)
{
  std::ostringstream ostr;
  ostr.imbue(std::locale::classic());
  int numSize(0);

  switch (format)
  {
    case 1:
      ostr.precision(precision);
      ostr.setf(std::ios::scientific, std::ios::floatfield);
      ostr << n;
      numSize = (int)ostr.str().length();
      return numSize;
    case 2:
      ostr << n;
      if ((std::ceil(n) - std::floor(n)) != 0.0)
      {
        ostr.precision(precision);
      }
      // Gets the length of the string with the current format without the end
      // character
      numSize = (int)ostr.str().length() - 1;
      return numSize;
    case 3:
      ostr.setf(ios::fixed, ios::floatfield);
      ostr << n / 1000;
      if ((std::ceil(n / 1000.0) - std::floor(n / 1000.0)) != 0.0)
      {
        ostr.precision(precision);
      }
      numSize = (int)ostr.str().length() - 1;
      return numSize + 1; // minus three zeros + K
    case 4:
      ostr.setf(ios::fixed, ios::floatfield);
      ostr << n / 1000;
      if ((std::ceil(n / 1000.0) - std::floor(n / 1000.0)) != 0.0)
      {
        ostr.precision(precision);
      }
      numSize = static_cast<int>(ostr.str().length() - 1);
      return numSize; // minus three zeros
    case 5:
      ostr.setf(ios::fixed, ios::floatfield);
      ostr << n / 1000000;
      if ((std::ceil(n / 1000000.0) - std::floor(n / 1000000.0)) != 0.0)
      {
        ostr.precision(precision);
      }
      numSize = (int)ostr.str().length() - 1;
      return numSize; // minus six zeros
    case 6:
      ostr.setf(ios::fixed, ios::floatfield);
      ostr << n / 1000000;
      if ((std::ceil(n / 1000000.0) - std::floor(n / 1000000.0)) != 0.0)
      {
        ostr.precision(precision);
      }
      numSize = (int)ostr.str().length() - 1;
      return numSize + 1; // minus six zeros + M
    case 7:
      ostr.setf(ios::fixed, ios::floatfield);
      ostr << n / 1000;
      if ((std::ceil(n / 1000.0) - std::floor(n / 1000.0)) != 0.0)
      {
        ostr.precision(precision);
      }
      numSize = (int)ostr.str().length() - 1;
      return numSize; // Three 0's get reduced
    case 8:
      ostr.precision(precision);
      ostr.setf(std::ios::scientific, std::ios::floatfield);
      ostr << n / 1000;
      numSize = (int)ostr.str().length();
      return numSize;
    default:
      return 0;
  }
}

// This methods determines the optimum notation, font size and orientation of
// labels from an exhaustive search
double vtkAxisExtended::Legibility(
  double lmin, double lmax, double lstep, double scaling, vtkVector<int, 3>& parameters)
{
  int numTicks = static_cast<int>((lmax - lmin) / lstep);
  std::vector<double> tickPositions(numTicks);
  int fontSizes[8] = { 8, 9, 10, 12, 14, 18, 20, 24 };
  for (int i = 0; i < numTicks; ++i)
  {
    tickPositions[i] = lmax + i * lstep;
  }

  // this->LabelLegibilityChanged = true;
  int bestFormat = 1;
  int bestOrientation = 0;
  int bestFontSize = this->DesiredFontSize;

  double bestLegScore = 0.0;

  for (int iFormat = 1; iFormat < 9; ++iFormat)
  {
    double formatLegSum = 0.0;
    for (int i = 0; i < numTicks; ++i)
    {
      formatLegSum += FormatLegibilityScore(tickPositions[i], iFormat);
    }

    // Average of label legibility scores
    formatLegSum = formatLegSum / numTicks;

    double eps = VTK_DBL_EPSILON * 100;
    int v = 1;
    double rem = fmod(lmin, lstep);
    if ((rem < eps || (lstep - rem) < eps) && lmin <= 0 && lmax >= 0)
    {
      v = 0;
    }
    else
    {
      v = 1; // v is 1 is lebelling includes zero
    }

    formatLegSum = 0.9 * formatLegSum + 0.1 * v;

    double fontLegSum = 0.0;

    // 8 font sizes are checked
    for (int fontIndex = 0; fontIndex < 8; ++fontIndex)
    {
      int iFont = fontSizes[fontIndex];
      if (iFont == this->DesiredFontSize)
      {
        fontLegSum = 1.0;
      }
      // fontSizes[0] is the minimum font size
      else if (iFont < this->DesiredFontSize && iFont >= fontSizes[0])
      {
        fontLegSum = 0.2 * (iFont - fontSizes[0] + 1) / (this->DesiredFontSize - fontSizes[0]);
      }
      else
      {
        fontLegSum = -100.0;
      }

      for (int iOrientation = 0; iOrientation < 2; ++iOrientation)
      {
        double orientLegSum = (iOrientation == 0) ? 1 : -0.5;
        // Here the gap between two consecutive labels is calculated as:
        // 2*Actual distance (in pixels) among two ticks - string lengths of
        // the two largest labels
        double overlapLegSum = 1.0;

        double legScore = (formatLegSum + fontLegSum + orientLegSum + overlapLegSum) / 4;
        if (legScore > bestLegScore)
        {
          if (numTicks > 1)
          {
            double fontExtent;
            if ((this->IsAxisVertical && iOrientation) || (!this->IsAxisVertical && !iOrientation))
            {
              fontExtent =
                (FormatStringLength(iFormat, tickPositions[numTicks - 1], this->Precision) +
                  FormatStringLength(iFormat, tickPositions[numTicks - 2], this->Precision)) *
                iFont;
            }
            else
            {
              fontExtent = iFont * 2;
            }
            double tickDistance = lstep * scaling;
            double labelingGap = 2 * (tickDistance)-fontExtent;
            // 1.1 for line spacing
            overlapLegSum = std::min(1.0, 2 - 3 * iFont * 1.1 / labelingGap);
            /*if(labelingGap > 3*iFont)
              {
              overlapLegSum = 1.0;
              }
            else if(labelingGap < 3*iFont && labelingGap > 0)
              {
              overlapLegSum = 2 - 3* iFont / labelingGap ;
              }
            else
              {
              overlapLegSum = -100;
              }*/
          }

          legScore = (formatLegSum + fontLegSum + orientLegSum + overlapLegSum) / 4;

          if (legScore > bestLegScore)
          {
            bestFormat = iFormat;
            bestOrientation = iOrientation;
            bestFontSize = iFont;
            bestLegScore = legScore;
          }
        }
      }
    }
  }

  parameters[0] = bestFormat;
  parameters[1] = bestFontSize;
  parameters[2] = bestOrientation;
  return bestLegScore;
}

// This method implements the algorithm given in the paper
vtkVector3d vtkAxisExtended::GenerateExtendedTickLabels(
  double dmin, double dmax, double m, double scaling)
{
  double Q[] = { 1, 5, 2, 2.5, 4, 3 };
  double w[] = { 0.25, 0.2, 0.5, 0.05 };
  double eps = VTK_DBL_EPSILON * 100;
  vtkVector3d ans;

  this->LabelLegibilityChanged = false;
  if (dmin > dmax)
  {
    double temp = dmin;
    dmin = dmax;
    dmax = temp;
  }

  if (dmax - dmin < eps)
  {
    ans[0] = dmin;
    ans[1] = dmax;
    ans[2] = m;
    // return Sequence(dmin,dmax, m);
    return ans;
  }

  int qLength = 6; // Q.Length(); // Hard Coded

  // list<double> best;
  double bestScore = -2;
  double bestLmin(0), bestLmax(0), bestLstep(0);

  const int INF = 100; // INT_MAX;  Again 100 is hard coded

  int j = 1;
  while (j < INF)
  {
    for (int qIndex = 0; qIndex < qLength; ++qIndex)
    {
      double sm = SimplicityMax(qIndex, qLength, j);
      if ((w[0] * sm + w[1] + w[2] + w[3]) < bestScore)
      {
        j = INF;
        break;
      }

      int k = 2;
      while (k < INF)
      {
        double dm = DensityMax(k, m);
        if ((w[0] * sm + w[1] + w[2] * dm + w[3]) < bestScore)
        {
          break;
        }
        double delta = (dmax - dmin) / ((k + 1) * j * Q[qIndex]);
        double z = ceil(log10(delta));
        while (z < INF)
        {
          double step = j * Q[qIndex] * pow(10.0, z);
          // double cm = CoverageMax(dmin, dmax, step*(k-1));
          if ((w[0] * sm + w[1] + w[2] * dm + w[3]) < bestScore)
          {
            break;
          }

          int minStart = static_cast<int>(std::floor(dmax / step) * j - (k - 1) * j);
          int maxStart = static_cast<int>(std::ceil(dmin / step) * j);

          if (minStart > maxStart)
          {
            ++z;
            continue;
          }

          for (int start = minStart; start <= maxStart; ++start)
          {
            double lmin = start * (step / j);
            double lmax = lmin + step * (k - 1);
            double lstep = step;

            double s = Simplicity(qIndex, qLength, j, lmin, lmax, lstep);
            double c = Coverage(dmin, dmax, lmin, lmax);
            double g = Density(k, m, dmin, dmax, lmin, lmax);

            double score = w[0] * s + w[1] * c + w[2] * g + w[3];

            if (score < bestScore)
              continue;

            // vtkVector<double,4> l = this->Legibility(lmin, lmax, lstep, scaling);

            vtkVector<int, 3> legibilityIndex;
            double newScore = this->Legibility(lmin, lmax, lstep, scaling, legibilityIndex);

            score = w[0] * s + w[1] * c + w[2] * g + w[3] * newScore;

            if (score > bestScore)
            {
              bestScore = score;
              bestLmin = lmin;
              bestLmax = lmax;
              bestLstep = lstep;
              this->LabelFormat = legibilityIndex[0]; // label format
              this->FontSize = legibilityIndex[1];    // label font size
              this->Orientation = legibilityIndex[2]; // label orientation
            }
          }
          ++z;
        }
        ++k;
      }
    }
    ++j;
  }
  ans[0] = bestLmin;
  ans[1] = bestLmax;
  ans[2] = bestLstep;
  // vtkVector3d answers(bestLmin, bestLmax, bestLstep);
  // return Sequence(bestLmin, bestLmax, bestLstep);
  return ans;
}

void vtkAxisExtended::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);
  os << indent << "Orientation: " << this->Orientation << endl;
  os << indent << "FontSize: " << this->FontSize << endl;
  os << indent << "DesiredFontSize: " << this->DesiredFontSize << endl;
  os << indent << "Precision: " << this->Precision << endl;
  os << indent << "LabelFormat: " << this->LabelFormat << endl;
}
VTK_ABI_NAMESPACE_END
